﻿using System;
using SoundEngine3.DspPlugins;
using SoundEngine3.DspPlugins.Meta;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine
{
    /// <summary>
    /// Parameter for module.
    /// </summary>
    public class ModuleParameter : ParameterPropertiesDirect
    {
        public bool InternallyVisible { get; set; }

        public BaseModule Module { get; private set; }

        public bool ShouldRender
        {
            get
            {
                bool flag = this.Module.IsTopModule();
                bool module = this.Module._IsRenderInTrackArea;
                if (!this.InternallyVisible)
                {
                    return true;
                }
                if (flag && !module)
                {
                    return false;
                }
                return true;
            }
        }

        public ModuleParameter(BaseModule module)
            : base(module.Manager.DspOwner)
        {
            Module = module;
        }

        public ModuleParameter(double min, double max, double init, BaseModule module)
            : base(module.Manager.DspOwner)
        {
            Module = module;

            MinValue = min;
            MaxValue = max;
            RawValue = init;
        }
    }

    public enum InputModuleType
    {
        /// <summary>
        /// User Input as buffer or GetDirect and SetDirect.
        /// </summary>
        InputBufferAndDirect,

        /// <summary>
        /// Use GetDirect and SetDirect events.
        /// </summary>
        OnlyDirect
    }

    /// <summary>
    /// Parameter that allows input connection for module. It will internally add a InputModule instance to the module.
    /// </summary>
    public class InputModuleParameter : ModuleParameter
    {
        public class Input_ModuleInput : ModuleInput
        {
            internal readonly InputModuleParameter Owner;

            public Input_ModuleInput(string name, IOType type, BaseModule module, InputModuleParameter owner)
                : base(name, type, module)
            {
                Owner = owner;
            }

            public Input_ModuleInput(string shortName, string name, IOType type, BaseModule module, InputModuleParameter owner)
                : base(shortName, name, type, module)
            {
                Owner = owner;
            }

            internal override void ResetupSwitchable(IOType type)
            {
                base.ResetupSwitchable(type);

                Owner.SetupAudioBuffer();
            }
        }

        private void SetupAudioBuffer()
        {
            int buffer = ModuleOutput.GetBufferLength(InternalInputModule);

            OnBufferSizeChanged(buffer);
        }

        private SoundEngine3.SoundEngineContext mContext;
        private double[] mValues;
        private double mLastFirstValue;

        public double FirstValue
        {
            get
            {
                return Values[0];
            }
        }

        public double[] Values
        {
            get
            {
                if (InternalInputModule.IsConnected)
                {
                    // Bypass to use this directly to increase performance.
                    return InternalInputModule.FromOutput.AudioBuffer.Buffer;
                }
                else
                {
                    return mValues;
                }
            }
        }

        public ModuleInput InternalInputModule { get; private set; }

        public InputModuleParameter(BaseModule module, string inputName, double initValue, IOType type)
            : this(module, inputName, initValue, type, InputModuleType.InputBufferAndDirect)
        {
        }

        public InputModuleParameter(BaseModule module, string inputName, double initValue, IOType type, InputModuleType imtype)
            : base(module)
        {
            OnBufferSizeChanged(module.Manager.DspOwner.SoundEngineContext.BufferSize);

            InternalInputModule = new Input_ModuleInput(inputName, type, module, this);

            DirectGet += delegate ()
            {
                return mValues[0];
            };
            DirectSet += delegate (double value)
            {
                SharpDepend.Classes.PeformanceHelper.MemSet(mValues, 0, mValues.Length, value);
            };
            RaiseDirectSet(initValue);
        }

        internal void Process()
        {
            if (mLastFirstValue != FirstValue)
            {
                RaiseDirectSet(FirstValue);
            }
            mLastFirstValue = FirstValue;
        }

        public double this[int index]
        {
            get
            {
                return Values[index];
            }
            set
            {
                Values[index] = value;
            }
        }

        internal void OnBufferSizeChanged(int bufferSize)
        {
            int buffer = InternalInputModule == null ? bufferSize : ModuleOutput.GetBufferLength(InternalInputModule);

            if (buffer == 0)
            {
                SharpDepend.Log.Assert(bufferSize > 0, "Buffer size can not be 0 or negative. Buffer size will be 1.");
                buffer = 1;
            }

            if (mValues == null || buffer != mValues.Length)
            {
                double value = (mValues == null || mValues.Length == 0) ? 0 : mValues[0];
                mValues = new double[buffer];
                SharpDepend.Classes.PeformanceHelper.MemSet(mValues, 0, mValues.Length, value);
            }
        }
    }

    /// <summary>
    /// Parameter that allows input connection for module. It will internally add a InputModule instance to the module.
    /// </summary>
    public class InputModuleEnumParameter<TEnum> : InputModuleParameter
            where TEnum : struct
    {
        private EnumParameter<TEnum> mEnumParameter;

        public InputModuleEnumParameter(BaseModule module, string inputName, double initValue, IOType type)
            : base(module, inputName, initValue, type)
        {
            mEnumParameter = new EnumParameter<TEnum>(module);

            var enumIndices = mEnumParameter.GetIndices();
            MinValue = 0;
            MaxValue = enumIndices.Length - 1;
            IsInteger = true;

            OnValueChanged += InputModuleEnumParameter_OnValueChanged;
        }

        private void InputModuleEnumParameter_OnValueChanged(ParameterProperties obj)
        {
            mEnumParameter.RawValue = (int)obj.RawValue;
        }

        public TEnum EnumValue
        {
            get
            {
                return mEnumParameter.EnumValue;
            }
            set
            {
                mEnumParameter.EnumValue = value;
            }
        }
    }
}
